/******************************************************************************
 * This file is part of libemb.
 *
 * libemb is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * libemb is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with libemb.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Project: Embedme
 * Author : FergusZeng
 * Email  : cblock@126.com
 * git	  : https://gitee.com/newgolo/embedme.git
 * Copyright 2014~2020 @ ShenZhen ,China
*******************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdlib.h>
#include "libemb/Tracer.h"
#include "libemb/Thread.h"
#include "libembx/HttpServer.h"

#define IS_SPACE(x) isspace((int)(x))
#define SERVER_STRING "Server: embedme-httpd/1.0.0\r\n"

namespace libembx{
using namespace libemb;
/**
 *  @brief  HttpServer构造函数
 *  @param  none
 *  @return none
 */
HttpServer::HttpServer():
m_rootPath("")
{
}
/**
 *  @brief  HttpServer析构函数
 *  @param  none
 *  @return none
 */
HttpServer::~HttpServer()
{
}

/**
 *  @brief  设置根路径
 *  @param  path 根路径
 *  @return none
 *  @note   none
 */
void HttpServer::setRootPath(const std::string path)
{
    m_rootPath = path;
}

void HttpServer::onBadRequest(TcpSocket& connSocket)
{
    std::string respond = "HTTP/1.0 400 BAD REQUEST\r\n";
    respond.append("Content-type: text/html\r\n");
    respond.append("\r\n");
    respond.append("<P>Your browser sent a bad request, ");
    respond.append("such as a POST without a Content-Length.\r\n");
    connSocket.sendData(CSTR(respond), respond.size(), 0);
}
void HttpServer::onNotFound(TcpSocket& connSocket)
{
    std::string respond = "HTTP/1.0 404 NOT FOUND\r\n";
    respond.append(SERVER_STRING);
    respond.append("Content-Type: text/html\r\n");
    respond.append("\r\n");
    respond.append("<HTML><TITLE>Not Found</TITLE>\r\n");
    respond.append("<BODY><P>The server could not fulfill\r\n");
    respond.append("your request because the resource specified\r\n");
    respond.append("is unavailable or nonexistent.\r\n");
    respond.append("</BODY></HTML>\r\n");
    connSocket.sendData(CSTR(respond), respond.size(), 0);
}

void HttpServer::onInternalServerError(TcpSocket& connSocket)
{
    std::string respond = "HTTP/1.0 500 Internal Server Error\r\n";
    respond.append("Content-type: text/html\r\n");
    respond.append("\r\n");
    respond.append("<P>Error prohibited CGI execution.\r\n");
    connSocket.sendData(CSTR(respond), respond.size(), 0);
}

void HttpServer::onUnimplemented(TcpSocket& connSocket)
{
    std::string respond = "HTTP/1.0 501 Method Not Implemented\r\n";
    respond.append(SERVER_STRING);
    respond.append("Content-Type: text/html\r\n");
    respond.append("\r\n");
    respond.append("<HTML><HEAD><TITLE>Method Not Implemented\r\n");
    respond.append("</TITLE></HEAD>\r\n");;
    respond.append("<BODY><P>HTTP request method not supported.\r\n");
    respond.append("</BODY></HTML>\r\n");
    connSocket.sendData(CSTR(respond), respond.size(), 0);
}

int HttpServer::readLine(TcpSocket& connSocket, char *buf, int size)
{
    int i = 0;
    char c = '\0';
    int n;

    while ((i < size - 1) && (c != '\n'))
    {
        n = connSocket.recvData(&c,1); 
        if (n > 0)
		{
            if (c == '\r')
			{
                connSocket.peekData(&c,1);
                if ((n > 0) && (c == '\n'))
				{
                    connSocket.recvData(&c,1);
                }
				else
				{
                    c = '\n';
                }
            }
            buf[i] = c;
            i++;
        } 
		else 
		{
            c = '\n';
        }
    }
    buf[i] = '\0';
    return(i);
}

void HttpServer::sendBack(TcpSocket& connSocket, const char *filename)
{
    FILE *fp = NULL;
    int numchars = 1;
    char buf[1024];

    buf[0] = 'A'; buf[1] = '\0';
    while ((numchars > 0) && strcmp("\n", buf))  /* 去掉头部(HEADER) */
    {
        numchars = readLine(connSocket, buf, sizeof(buf));
    }
    fp = fopen(filename, "r");
    if (fp == NULL)
    {
        onNotFound(connSocket);
    }
    else
    {
        /* 写入HTTP头部 */
        std::string httpHeader = "HTTP/1.0 200 OK\r\n";
        httpHeader.append(SERVER_STRING);
        httpHeader.append("Content-Type: text/html\r\n");
        httpHeader.append("\r\n");
        connSocket.sendData(httpHeader.c_str(), httpHeader.size(), 0);
        /* 写入HTML文件 */
        memset(buf,0,sizeof(buf));
        fgets(buf, sizeof(buf), fp);
        while (!feof(fp))
        {
            connSocket.sendData(buf, sizeof(buf),0);
            fgets(buf, sizeof(buf), fp);
        }
    }
    fclose(fp);
}
void HttpServer::executeCgi(TcpSocket& connSocket, const char *path,const char *method, const char *query_string)
{
    char buf[1024];
    int cgi_output[2];
    int cgi_input[2];
    pid_t pid;
    int status;
    int i;
    char c;
    int numchars = 1;
    int content_length = -1;

    buf[0] = 'A'; buf[1] = '\0';
    if (strcasecmp(method, "GET") == 0)
    {
        while ((numchars > 0) && strcmp("\n", buf))  /* 去掉头部(HEADER) */
        {
            numchars = readLine(connSocket, buf, sizeof(buf));
        }
    } else { /* POST */
        numchars = readLine(connSocket, buf, sizeof(buf));
        while ((numchars > 0) && strcmp("\n", buf))
        {
            buf[15] = '\0';
            if (strcasecmp(buf, "Content-Length:") == 0){
                content_length = atoi(&(buf[16]));
            }
            numchars = readLine(connSocket, buf, sizeof(buf));
        }
        if (content_length == -1) {
            onBadRequest(connSocket);
            return;
        }
    }

    sprintf(buf, "HTTP/1.0 200 OK\r\n");
    connSocket.sendData(buf, sizeof(buf),0);
    if (pipe(cgi_output) < 0) {
        onInternalServerError(connSocket);
        return;
    }
    if (pipe(cgi_input) < 0) {
        onInternalServerError(connSocket);
        return;
    }
    if ( (pid = fork()) < 0 ) {
        onInternalServerError(connSocket);
        return;
    }
    if (pid == 0)  /* 子进程 */
    {
        char meth_env[255];
        char query_env[255];
        char length_env[255];

        dup2(cgi_output[1], 1); /* 将标准输出复制给cgi输出管道的写端 */
        dup2(cgi_input[0], 0);  /* 将标准输入复制给cgi输入管道的读端 */
        close(cgi_output[0]);   /* 关闭输出读端(输出读端在父进程中) */
        close(cgi_input[1]);    /* 关闭输入写端(输入写端在父进程中) */
        sprintf(meth_env, "REQUEST_METHOD=%s", method);
        putenv(meth_env);
        if (strcasecmp(method, "GET") == 0) {
            sprintf(query_env, "QUERY_STRING=%s", query_string);
            putenv(query_env);
        } else {   /* POST */
            sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
            putenv(length_env);
        }
        /* 执行cgi程序 */
        execl(path, path, NULL);
        exit(0);
    } else {
        close(cgi_output[1]); /* 关闭输出写端(输出写端在子进程中) */
        close(cgi_input[0]);  /* 关闭输入读端(输入读端在子进程中) */
        if (strcasecmp(method, "POST") == 0){
            /* 读取客户端的POST数据,传给cgi子进程 */
            for (i = 0; i < content_length; i++) 
            { 
                connSocket.recvData(&c,1);
                write(cgi_input[1], &c, 1);
            }
        }
        /* 读取cgi子进程的输出,返回给客户端 */
        while (read(cgi_output[0], &c, 1) > 0)
        {
            connSocket.sendData(&c,1,0);
        }
        close(cgi_output[0]);
        close(cgi_input[1]);
        waitpid(pid, &status, 0);
    }
}
void HttpServer::acceptRequest(TcpSocket& connSocket)
{
    TRACE_DBG_CLASS("enter acceptRequest.\n");
    char buf[1024];
    int numchars;
    char method[255];
    char url[255];
    char path[512];
    size_t i, j;
    struct stat st;
    int cgi = 0;
    char *query_string = NULL;

    numchars = readLine(connSocket, buf, sizeof(buf));
    i = 0; j = 0;
    while (!IS_SPACE(buf[j]) && (i < sizeof(method) - 1))
    {
        method[i] = buf[j];
        i++;
        j++;
    }
    method[i] = '\0';

    if (strcasecmp(method, "GET") && strcasecmp(method, "POST")){
        onUnimplemented(connSocket);
        return;
    }

    if (strcasecmp(method, "POST") == 0){
        cgi = 1;
    }

    i = 0;
    while (IS_SPACE(buf[j]) && (j < sizeof(buf)))
    {
        j++;
    }
    while (!IS_SPACE(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
    {
        url[i] = buf[j];
        i++; 
        j++;
    }
    url[i] = '\0';
    if (strcasecmp(method, "GET") == 0){
        query_string = url;
        while ((*query_string != '?') && (*query_string != '\0'))
        {
            query_string++;
        }
        if (*query_string == '?')
        {
            cgi = 1;
            *query_string = '\0';
            query_string++;
        }
    }

    sprintf(path, "%s%s",m_rootPath.c_str(),url);
    if (path[strlen(path) - 1] == '/'){
        strcat(path, "index.html");
    }
    TRACE_DBG_CLASS("-->method[%s],url:[%s],path:[%s].\n",method,url,path);
    if (stat(path, &st) == -1) {
        while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
        {
            numchars = readLine(connSocket, buf, sizeof(buf));
        }
        onNotFound(connSocket);
    } else {
        if ((st.st_mode & S_IFMT) == S_IFDIR) {
            strcat(path, "/index.html");
        }
        if ((st.st_mode & S_IXUSR) ||
            (st.st_mode & S_IXGRP) ||
            (st.st_mode & S_IXOTH)) {
            cgi = 1;
        }
        if (!cgi) {
            sendBack(connSocket, path);
        } else {
            executeCgi(connSocket, path, method, query_string);   
        }
    }
    TRACE_DBG_CLASS("exit acceptRequest.\n");
}

bool HttpServer::onNewConnection(std::unique_ptr<TcpSocket> client)
{
    acceptRequest(*client);
    client->close();
    return true;
}
}
